package solution;

import java.util.ArrayList;

import hex.*;
import game.*;
import game.GameState.Who;

public class TedsHexPlayer extends GamePlayer {
	public static char me;
	public static char enemy;
	
	public TedsHexPlayer(String n) {
		super(n, new HexState(), false);
	}
	
	
	public void init() {
		
		
	}
	
	
	public void startGame(String opponent) {}
	
	
	public void timeOfLastMove(double secs) {}
	
	
	public void endGame(int result) {
		 prevMoves = new ArrayList<TriPoint>();
		 pairs = new ArrayList<PointPair>();
		 firstMove = true;
		 secondMove = false;
	}
	
	
	public void done() { }
	
	public static char getWho(){return me;}
	public static char getEnemy(){return enemy;}
	
	boolean firstMove = true;
	boolean secondMove = true;
	public ArrayList<TriPoint> prevMoves = new ArrayList<TriPoint>();
	ArrayList<PointPair> pairs = new ArrayList<PointPair>();
	
	
	
	/*
	 * Rotates the board if we are on the black team;
	 * @params: The board as a 2d character array.
	 * @return: The board rotated pi/2 to the right if we are black.
	 */
	public static char[][] orientBoard(char[][] board){
		if (TedsHexPlayer.getEnemy() == 'W')
			return board;
		char[][] newBoard = new char[11][11];
		for (int m = 0; m < 11; m++)
			for (int n= 0; n < 11; n++)
				newBoard[m][n] = board[n][m];
		return newBoard;
	}
	
	
	
	public GameMove getMove(GameState state, String lastMove) {
		if (side==Who.HOME){
			me = HexState.homeSym;
			enemy = HexState.awaySym;
		}
		else {
			enemy = HexState.homeSym;
			me = HexState.awaySym;
		}
		
		
		
		HexState board = (HexState)state;
		
		if (firstMove){
			firstMove = false;
			TriPoint tp = new TriPoint(5,3);
			TriPoint tp2 = new TriPoint(5,4);
			if (state.moveOK(tp.toHexMove())){
				prevMoves.add(tp);
				return tp.toHexMove();
			}
			prevMoves.add(tp2);
			return tp.toHexMove();
		}
		if (secondMove){
			secondMove = false;
			TriPoint tp = new TriPoint(4,4);
			TriPoint tp2 = new TriPoint(6,3);
			if (state.moveOK(tp.toHexMove())){
				prevMoves.add(tp);
				return tp.toHexMove();
			}
			prevMoves.add(tp2);
			return tp.toHexMove();
		}
		
		
		
		TriPoint p = getBestPoint(board);
		
		if (!board.moveOK(p.toHexMove()))
			p = random(board);
		
		prevMoves.add(p);
		
		return p.toHexMove();
	}
	
	
	
	
	
	
	
	
	public TriPoint getBestPoint(HexState board){
		for (PointPair pp : pairs){
			System.out.println(pp.contended(board.board));
			if (pp.contended(board.board)){
				System.out.println("DEFEND THE LINE!!!");
				return pp.getUnoccupiedPoint(board.board);
			}
		}
		
		int sdd = board.board.length-1;
		
		
		int minTop = 999;
		int minBottom = 999;
		TriPoint MTPoint = null;
		TriPoint MBPoint = null;
		
		boolean connectedTop = false;
		boolean connectedBottom = false;
		TriPoint topConnector = null;//REMOVE!?!?!?!?!?!?!?1?!?!?!?!?1?1?!?!?!?!?!?!?!?!?!?!??!?!?
		TriPoint bottomConnector = null;//REMOVE!??!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!??!?!?!?!?
		
		for(TriPoint tp : prevMoves){
			if (tp.x<7){
				int i = tp.distanceFromSide(0, 0, board.board, null);
				if (i==0){
					connectedTop = true;
					topConnector = tp;
				}
				else if (i<minTop && !connectedTop){
					minTop = i;
					MTPoint = tp;
				}
			}
			if (tp.x>3){
				int i = tp.distanceFromSide(sdd, 0, board.board, null);
				if (i==0){
					connectedBottom = true;
					bottomConnector = tp;
				}
				else if (i<minBottom && !connectedBottom){
					minBottom = i;
					MBPoint = tp;
				}
			}
		}
		
		if(connectedTop&&connectedBottom){
			System.out.println("CONNECTED :D");
			for(PointPair pp: pairs)
			{
				if(TriPoint.getBothPathsBetweenPoints(pp.a, pp.b)[0].isEmpty(board.board))
					return TriPoint.getBothPathsBetweenPoints(pp.a, pp.b)[0];
				
				if(TriPoint.getBothPathsBetweenPoints(pp.a, pp.b)[1].isEmpty(board.board))
					return TriPoint.getBothPathsBetweenPoints(pp.a, pp.b)[1];
			}
		}
		if (connectedTop){
			MTPoint = null;
		}
		if (connectedBottom){
			MBPoint = null;
		}
		
		TriPoint nextBase = this.getFartherPoint(MTPoint, MBPoint, minTop, minBottom, board);
		int x = nextBase.equals(MTPoint) ? 0 : sdd;
		ArrayList<TriPoint> tps = removeOccupied(getOnsiesAndTwosies(nextBase, x),board.board,nextBase);
		if (tps.size()==0){
			prevMoves.remove(nextBase);
			return this.getBestPoint(board);
		}
		int min = 99999;
		TriPoint btp = null;
		
		for(TriPoint tp : tps){
			int i = tp.distanceFromSide(x, 0, board.board, null);
			if (i<min){
				min = i;
				btp = tp;
			}
		}
		
		if (board.moveOK(btp.toHexMove())){
			if (TriPoint.getDist(btp, nextBase)==2)pairs.add(new PointPair(btp,nextBase));
			return btp;
		}
		
		//If for some reason we have exhausted all strategies / something has broken
		//play a random move
		return random(board);
		
	}
	/*
	 * This is an emergency backup method that returns a random point that is playable on the board
	 * @param hs, The HexState the game is in, so the board is accessible
	 * @return a random point on the grid (is playable)
	 */
	public TriPoint random(HexState hs){
		int i = hs.board.length;
		TriPoint tp = new TriPoint(r(i),r(i));
		while(!hs.moveOK(tp.toHexMove()))tp = new TriPoint(r(i),r(i));
		return tp;
	}
	/*
	 * Helper method for TriPoint random(HexState hs)
	 * Makes code look neater
	 * @param upperbound non-inclusive for random
	 * @return a random int from 0 to i (not including i)
	 */
	private static int r(int i){
		return ((int)(Math.random()*i));
	}
	
	/*
	 * @param topPoint : the point 
	 */
	public TriPoint getFartherPoint(TriPoint topPoint, TriPoint bottomPoint, int topDist, int bottomDist,HexState hs){
		if (topPoint==null && bottomPoint == null)return random(hs);
		if (topPoint == null)return bottomPoint;
		if (bottomPoint == null)return topPoint;
		if (bottomDist>topDist)return bottomPoint;
		return topPoint;
	}
	
	
	/*
	 * @param TriPoint t : the point that is close and we are testing with
	 * @param int sideTwoards : the side towards which we are playing: 0=top, other=bottom
	 * 
	 * gets the points towards the side that are one and two moves away from the move, unoccupied and blocked points included
	 */
	public TriPoint[] getOnsiesAndTwosies(TriPoint t, int sideTowards){
		if (sideTowards==0){
			TriPoint[] res = {new TriPoint(t.x-1,t.y-1), new TriPoint(t.x-1,t.y), new TriPoint(t.x-1,t.y+1), new TriPoint(t.x-1,t.y+2), new TriPoint(t.x-2,t.y+1)};
			return res;
		}
		TriPoint[] res = {new TriPoint(t.x+1,t.y-2), new TriPoint(t.x+1,t.y-1), new TriPoint(t.x+1,t.y), new TriPoint(t.x+1,t.y), new TriPoint(t.x+2,t.y-1)};
		return res;
	}
	
	
	
	/*
	 * @param tps : ThePointSystem -- all the points that are potential moves
	 * @param board: the board to test the points against
	 * @param tpf : ThePointFrom-before  -- the base Point from which the potential moves were generated
	 * 
	 * this adds all the unoccupied or available points to an arrayList and returns that list
	 */
	
	public ArrayList<TriPoint> removeOccupied(TriPoint[] tps, char[][] board, TriPoint tpf){ //get those protesters out of our java file!
		ArrayList<TriPoint> res = new ArrayList<TriPoint>();
		for (TriPoint tp : tps){
			if (tp.isEmpty(board)){
				if(TriPoint.getDist(tp, tpf)==1)res.add(tp);
				if(TriPoint.getDist(tp, tpf)==2){
					if (TriPoint.hasTwoOpenMovesBetween(tpf, tp, board)){
						res.add(tp);
					}
				}
			}
		}
		return res;
	}
	
	
	public static void main(String [] args) {
		GamePlayer p = new TedsHexPlayer("S3xy-sB3asT (simsbury High School)");
		p.compete(args, 1);
	}
}







class TriPoint{
	//Note that even though these are denoted as 'x' and 'y'
	//x corresponds to row and y corresponds to column 
	int x,y;
	public TriPoint(int x, int y){
		this.x = x; this.y = y;
	}
	public boolean equals(TriPoint o){
		return o!=null && o.x==x && o.y==y;
	}
	/*
	 * @return the distance in hex boxes from a to b 
	 */
	public static int getDist(TriPoint a, TriPoint b){
		int distance = (Math.abs (b.x - a.x) + Math.abs (b.y - a.y) + Math.abs(b.x - a.x + b.y - a.y)) / 2;
		return distance;		
	}
	/*
	 * @precondition points f and s have 2 moves to connect them and are a distance of 2 away
	 * @return a size 2 array containing both points between f and s
	 */
	public static TriPoint[] getBothPathsBetweenPoints(TriPoint f, TriPoint s){
		int x = f.x+s.x, y = f.y+s.y;
		int xx = x/2, yy = y/2;
		TriPoint[] i = new TriPoint[2];
		if(f.x - s.x > 1)
		{
			i[0] = new TriPoint(xx,yy);
			i[1] = new TriPoint(x-xx, y-yy);
		}
		else
		{
			i[0] = new TriPoint(x-xx, yy); 
			i[1] = new TriPoint(xx, y-yy);
		}
		return i;
	}
	/*
	 * Checks if points f and s have two open moves between them.
	 * Note that this method differs from PointPair's contended(char[][] board) because
	 *   contended(char[][] board) checks if one of the points is ours and returns true if this is so.
	 *   While this method is only checking if the opposing team has played there. 
	 *   (tldr; will not check connectivity, possible for this to return false, but be connected)
	 * @return true if there are two open spots, false for all other conditions 
	 * 
	 * --this method is just for convenience and actually uses the implementation in the next Method
	 */
	public static boolean hasTwoOpenMovesBetween(TriPoint f, TriPoint s, HexState state){
		return TriPoint.hasTwoOpenMovesBetween(f, s, state.board);
	}
	
	/*
	 * @precondition: the points are distance 2 apart
	 * 
	 * @param TriPoint f: the first Point
	 * @Param TriPoint s: the second Point
	 * @param board: the board to be tested on
	 */
	public static boolean hasTwoOpenMovesBetween(TriPoint f, TriPoint s, char[][] board){
		if (getDist(f,s)!=2)return false;
		TriPoint[] t = getBothPathsBetweenPoints(f,s);
		if (board[t[0].x][t[0].y]==TedsHexPlayer.getEnemy())return false;
		if (board[t[0].x][t[0].y]==TedsHexPlayer.getEnemy())return false;
		return true;
	}
	/*
	 * if the point is outside the board, it returns as though it was occupied
	 * @param board: the board to be checked
	 */
	public boolean isEmpty(char[][] board){
		if(x<0 || y<0 || x>=board.length || y>=board[0].length)return false;
		return board[x][y] == HexState.emptySym;
	}
	/*
	 * converts a TriPoint to a HexMove
	 * @return the equivalent HexMove of this TriPoint
	 */
	public HexMove toHexMove(){
		return new HexMove(x,y);
	}
	
	
	
	/*
	 * @param sidenum: either 0 or the opposite end of the grid (usually 10)
	 * @param count: should always be called with 0
	 * 
	 * if the point has NO paths to the edge, it return a big number :D
	 */
	public int distanceFromSide(int sidenum, int count, char[][] board, TriPoint last){
		if (this.x==sidenum)return count;  //base case: the point is ON the edge, return its distance
		
		TriPoint[] legit;
		if (this.x>sidenum){  //going towards the top
			TriPoint[] p = {new TriPoint(x,y-1), new TriPoint(x-1,y), new TriPoint(x-1, y+1), new TriPoint(x, y+1)};  //hardcoded the 5 points
			legit = p;
		}
		else{  //going towards the bottom
			TriPoint[] p = {new TriPoint(x,y-1), new TriPoint(x+1,y), new TriPoint(x+1, y-1), new TriPoint(x, y+1)}; //hardcoded the 5 points
			legit = p;
		}
		
		
		ArrayList<TriPoint> tps = new ArrayList<TriPoint>();
		for (TriPoint p : legit)
			if (p.isEmpty(board) && !p.equals(last))
				tps.add(p);//adds the point if it is a real point, not the last point, and is empty
				
		if (tps.size()==0)return 9999999;  //heres that really big number 
		
		
		
		int[] distances = new int[tps.size()];
		for (int i = 0; i < tps.size(); i++)  //gets the point closest to the target side
			distances[i] = tps.get(i).distanceFromSide(sidenum, count+1, board, this);
		
		
		
		int min = 999999;
		for (int i : distances)if (i<min)min=i;
		return min;
	}
}







/*
 * A point pair represents 2 points that are both a distance of 2 from each other
 * as well as have 2 moves to connect them. 
 * Example pairs are (3, 2) and (2, 4)
 */
class PointPair{
	TriPoint a;
	TriPoint b;
	/*
	 * @param a an arbitrary point
	 * @param b another arbitrary point b distnce 2 from a
	 * 
	 * @precondition b is distance 2 from a
	 */
	public PointPair(TriPoint a, TriPoint b){
		this.a = a;
		this.b = b;
	}
	/*
	 * @param board is the same board as accessible via HexState.board
	 * @return true if the point pair is lacking 
	 */
	public boolean contended(char[][] board){
		TriPoint[] ropes = TriPoint.getBothPathsBetweenPoints(a, b);
		System.out.println(TedsHexPlayer.getWho()+"      "+TedsHexPlayer.getEnemy());
		if (board[ropes[0].x][ropes[0].y]==TedsHexPlayer.getWho())return false;
		if (board[ropes[1].x][ropes[1].y]==TedsHexPlayer.getWho())return false;
		if (board[ropes[0].x][ropes[0].y]==TedsHexPlayer.getEnemy())return true;
		if (board[ropes[1].x][ropes[1].y]==TedsHexPlayer.getEnemy())return true;
		return false;
	}
	/*
	 * @precondition -it IS contended
	 * @return the point that is unoccupied
	 * Note that because this is constantly checked, if a point pair is valid
	 *   then getUnoccupiedPoint should always properly return a point so long as it is contested
	 */
	public TriPoint getUnoccupiedPoint(char[][] board){
		TriPoint[] ropes = TriPoint.getBothPathsBetweenPoints(a, b);
		if (board[ropes[0].x][ropes[0].y]==HexState.emptySym)return ropes[0];
		return ropes[1];
	}
}



